package com.jeasonzhao.commons.contrib;

import java.io.FileNotFoundException;
import java.io.IOException;
import com.jeasonzhao.commons.basic.StringCollection;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.jeasonzhao.commons.utils.SystemUtil;

public class ClearMakefile
{
    private static final String SYMBOL_PBS = "PBS ";
    private static final String SYMBOL_SOURCES = "SRCS ";
    private static final String SYMBOL_MAKEFILEINCLUDE = "include ";
    private static final String SYMBOL_INCLUDES = "INCLUDES ";
    //private static final String SYMBOL_LINKS = "PROJLIBS ";
    private static final String SYMBOL_HEADFILES = "HDRS ";
    private static final int TRACE = 0,DEBUG = 1,INFORMATION = 2,ERROR = 3;

    private int errorLevel = TRACE;
    private ClearMakefile parent = null;
    private java.util.Vector<ClearMakefile> children =null;
    private java.util.Hashtable<String,String> originalVariables = null;
    private java.util.Hashtable<String,ClearMakefile> parsedMakefiles=null;
    private StringCollection originalChildrenMakefiles = null;
    private StringCollection originalChildrenFolders = null;
    private String myDirectoryPath = null;
    private String myDirectoryName=null;
    private String myFileName=null;
    private StringCollection originalSourceFileNames = null;
    private StringCollection originalIncludePathName = null;

    private ClearMakefile(ClearMakefile parent,String fileName,int nlevel)
    {
        this.parent = parent;
        if(null!=fileName)
        {
            fileName=fileName.trim();
            java.io.File file = new java.io.File(fileName);
            file = file.getAbsoluteFile();
            this.myFileName = file.getAbsolutePath();
            this.myDirectoryPath = file.getParent();
            if(this.myDirectoryPath.endsWith("/") == false && this.myDirectoryPath.endsWith("\\") == false)
            {
                this.myDirectoryPath += "/";
            }
            this.myDirectoryName = (new java.io.File(myDirectoryPath)).getName();
        }
        this.originalVariables = new java.util.Hashtable<String,String>();
        this.originalChildrenMakefiles = new StringCollection();
        this.originalChildrenFolders = new StringCollection();
        this.originalSourceFileNames = new StringCollection();
        this.originalIncludePathName = new StringCollection();
        this.children = new java.util.Vector<ClearMakefile>();
        this.parsedMakefiles=new java.util.Hashtable<String,ClearMakefile>();
        this.errorLevel =nlevel;
    }

    private void log(int level,String str)
    {
        if(this.errorLevel > level)
        {
            return;
        }
        String levelm = "UNKNOWN";
        switch(level)
        {
            case DEBUG:
                levelm = "DEBUG";
                break;
            case TRACE:
                levelm = "TRACE";
                break;
            case ERROR:
                levelm = "ERROR";
                break;
            case INFORMATION:
                levelm = "INFO";
                break;
            default:
                levelm = "UNKNOWN";
                break;

        }
        levelm="["+this.myDirectoryName+"]"+"["+levelm+"]";
        if(level >= ERROR)
        {
            System.err.println((new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss"))
                               .format(new java.util.Date())
                               + levelm + " " + str+" [fileName: "+this.myFileName+"]");

        }
        else
        {
            System.out.println((new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss"))
                               .format(new java.util.Date())
                               + levelm + " " + str);
        }
    }

    private String removeComments(String line)
    {
        if(null == line || line.trim().length() < 1)
        {
            return null;
        }
        String line2 = "";
        char[] ary = line.toCharArray();
        boolean instring = false;
        for(int n = 0;n < ary.length;n++)
        {
            char c = ary[n];
            if(c == '#' && instring == false)
            {
                break;
            }
            else if(c == '\'' || c == '\"')
            {
                instring = !instring;
                line2 += c;
            }
            else
            {
                line2 += c;
            }
        }
        return line2;
    }

    private String getVariableValue(String key)
    {
        String value = null;
        if(null==key)
        {
            return null;
        }
        else
        {
            key=key.toUpperCase().trim();
        }
        if(null != key && key.equalsIgnoreCase("BT"))
        {
            value = "D:\\copspf2\\includes";
        }
        else if(null != this.originalVariables && this.originalVariables.contains(key))
        {
            value = this.eval(this.originalVariables.get(key));
        }
        else
        {
            value = SystemUtil.get(key);
        }
        if(null != value)
        {
            value = value.replaceAll("\\\\","\\\\\\\\");
        }
//        else
//        {
//            log(ERROR,"Unknown variable "+key);
//        }
        return value;
    }

    private String eval(String str)
    {
        String regEx = "((\\$\\([^\\(\\)\\{\\}]+\\))|(\\$[\\w_]+)|(\\$\\{[^\\(\\)\\{\\}]+\\}))";
        Pattern reg = Pattern.compile(regEx);
        Matcher m = reg.matcher(str);
        java.util.ArrayList<String> ary = new java.util.ArrayList<String>();
        while(m.find())
        {
            String s = m.group(1);
            if(s != null && ary.contains(s.toUpperCase()) == false)
            {
                ary.add(s.toUpperCase());
            }
        }
        for(String key : ary)
        {
            String rkey = key.replaceAll("\\$","\\\\\\$")
                .replaceAll("\\(","\\\\\\(")
                .replaceAll("\\)","\\\\\\)")
                .replaceAll("\\{","\\\\\\{")
                .replaceAll("\\}","\\\\\\}");
            if(key.charAt(0)=='$')
            {
                key = key.substring(1);
            }
            if(key.startsWith("{") || key.startsWith("("))
            {
                key = key.substring(1);
            }
            if(key.endsWith("}") || key.endsWith(")"))
            {
                key = key.substring(0,key.length() - 1);
            }
            String value = this.getVariableValue(key);
            if(null == value)
            {
                log(ERROR,"No variable named " + key);
                value = "";
            }
            else
            {
                log(TRACE,key + "=" + value);
            }
            str = str.replaceAll(rkey,value);
        }
        return str;
    }
    private boolean fileHasBeenParsed(String fileName)
    {
        ClearMakefile p = this.getRootMakefile();
        if(p.parsedMakefiles.contains(fileName))
        {
            return true;
        }
        else
        {
            return false;
        }
    }

    private ClearMakefile getRootMakefile()
    {
        ClearMakefile p = this;
        while(p != null)
        {
            if(p.parent == null)
            {
                return p;
            }
            p = p.parent;
        }
        return this;
    }
    private void merge(ClearMakefile m)
    {
        if(null!=m)
        {
            for(java.util.Map.Entry<String,String> e: m.originalVariables.entrySet())
            {
                if(this.originalVariables.containsKey(e.getKey())==false)
                {
                    this.originalVariables.put(e.getKey(),e.getValue());
                }
            }
        }
    }
    private void parseFile()
        throws FileNotFoundException,IOException
    {
        if((new java.io.File(this.myFileName)).exists() == false)
        {
            throw new FileNotFoundException("File not found: " + this.myFileName);
        }
        log(INFORMATION,"==================== Parsing file " + this.myFileName + " ...");
//        log(INFORMATION,"[ROOT]>>"+ this.getRootMakefile().makefileName);
        parseContent();

        if(null!=this.originalChildrenMakefiles)
        {
            for(String fileName : this.originalChildrenMakefiles)
            {
                fileName = this.eval(fileName);
                ClearMakefile p = new ClearMakefile(this,fileName,this.errorLevel);
                if(this.fileHasBeenParsed(p.myFileName) == false)
                {
                    if((new java.io.File(p.myFileName)).exists())
                    {
                        p.parseFile();
                        this.children.add(p);
                        this.getRootMakefile().parsedMakefiles.put(p.myFileName,p);
                    }
                }
                else
                {
                    this.children.add(this.getRootMakefile().parsedMakefiles.get(p.myFileName));
                    log(INFORMATION,"Skip file " + p.myFileName);
                }
            }
        }
        //Merge variables
        if(null!=this.children)
        {
            for(ClearMakefile mp : this.children)
            {
                this.merge(mp);
            }
        }
        if(null!=this.originalChildrenFolders)
        {
            for(String fileName : this.originalChildrenFolders)
            {
                fileName = this.eval(fileName);
                if(false == fileName.startsWith("/"))
                {
                    fileName = this.myDirectoryPath + fileName;
                }
                fileName += "/Makefile";
                ClearMakefile p = new ClearMakefile(this,fileName,this.errorLevel);
                if(this.fileHasBeenParsed(p.myFileName) == false)
                {
                    if((new java.io.File(p.myFileName)).exists())
                    {
                        p.parseFile();
                        this.children.add(p);
                        this.getRootMakefile().parsedMakefiles.put(p.myFileName,p);
                    }
                }
                else
                {
                    log(INFORMATION,"Skip file " + p.myFileName);
                }
            }
        }
        this.getRootMakefile().parsedMakefiles.put(this.myFileName,this);
    }

    private void parseContent()
        throws IOException
    {
        java.io.BufferedReader r = new java.io.BufferedReader(new java.io.FileReader(this.myFileName));
        try
        {
            String line = null;
            while(true)
            {
                line = r.readLine();
                if(null == line)
                {
                    break;
                }
                line = line.trim();
                if(line.length() < 1 || line.startsWith("#"))
                {
                    log(TRACE,"Skip comment line: " + line);
                    continue;
                }
                log(TRACE,"Original line > " + line);
                line = removeComments(line);
                if(line.endsWith("\\"))
                {
                    line = line.substring(0,line.length() - 1).trim();
                    String s = null;
                    while(true)
                    {
                        s = r.readLine();
                        if(null == s || s.trim().length() < 1)
                        {
                            break;
                        }
                        s = s.trim();
                        //debug("Append line > "+line);
                        if(false == s.endsWith("\\"))
                        {
                            line += " " + s;
                            break;
                        }
                        else
                        {
                            line += " " + s.substring(0,s.length() - 1).trim();
                        }
                    }
                }
                //Remove extra spaces from the original line.
                line = line.replaceAll("\\s+"," ");
                log(TRACE,"Finalized Line: " + line);
                if(line.startsWith(SYMBOL_MAKEFILEINCLUDE))
                {
                    String fileName = parseLineValue(line,SYMBOL_MAKEFILEINCLUDE);
                    this.originalChildrenMakefiles.add(fileName);
                    if(this.originalChildrenMakefiles != null)
                    {
                        log(DEBUG,"Add a included make file: " + this.originalChildrenMakefiles.toString());
                    }
                }
                else if(line.startsWith(SYMBOL_PBS))
                {
                    this.originalChildrenFolders = StringCollection.splitString(parseLineValue(line,SYMBOL_PBS)," ");
                    if(this.originalChildrenFolders!=null)
                    {
                        log(DEBUG,"Add sub folders : " + this.originalChildrenFolders.toString());
                    }
                }
                else if(line.startsWith(SYMBOL_SOURCES))
                {
                    String files = parseLineValue(line,SYMBOL_SOURCES);
                    StringCollection sc=StringCollection.splitString(files," ");
                    if(null!=sc)
                    {
                        this.originalSourceFileNames.addAll(sc);
                    }
                    if(null!=this.originalSourceFileNames)
                    {
                        log(DEBUG,"Add source files : " + this.originalSourceFileNames.toString());
                    }
                }
                else if(line.startsWith(SYMBOL_HEADFILES))
                {
                    String files = parseLineValue(line,SYMBOL_HEADFILES);
                    StringCollection cs=StringCollection.splitString(files," ");
                    if(null!=cs)
                    {
                        this.originalSourceFileNames.addAll(cs);
                    }
                    log(DEBUG,"Add header files : " + this.originalSourceFileNames.toString());
                }
                else if(line.startsWith(SYMBOL_INCLUDES))
                {
                    String files = parseLineValue(line,SYMBOL_INCLUDES);
                    StringCollection sc=StringCollection.splitString(files," ");
                    if(sc!=null)
                    {
                        for(String f : sc)
                        {
                            if(f.startsWith("-I"))
                            {
                                f = f.substring("-I".length()).trim();
                            }
                            this.originalIncludePathName.add(f);
                        }
                        log(DEBUG,"Add includes files : " + this.originalIncludePathName.toString());
                    }
                }
                else if(line.indexOf("=") > 0)
                {
                    log(DEBUG,"Variable: " + line);
                    int n = line.indexOf("=");
                    String name = line.substring(0,n).trim();
                    String value = line.substring(n + 1).trim();
                    if(name.endsWith("+") || name.endsWith(":"))
                    {
                        name = name.substring(0,name.length() - 1).trim();
                    }
                    this.originalVariables.put(name.toUpperCase(),value);
                }
                else
                {
                    log(TRACE,"UNKNOWN: " + line);
                }
            }
        }
        finally
        {
            if(null != r)
            {
                r.close();
            }
        }
    }

    private String parseLineValue(String line,String key)
    {
        if(null == line || line.equals(key) || line.trim().startsWith(key) == false)
        {
            return line;
        }
        else
        {
            line = line.substring(key.length()).trim();
            if(line.startsWith("="))
            {
                line = line.substring(1).trim();
            }
            else if(line.startsWith(":="))
            {
                line = line.substring(2).trim();
            }
            return line;
        }
    }
    public void listAllSourceFolders()
    {
        //java.util.ArrayList<String> ret=new java.util.ArrayList<String>();
        for(ClearMakefile p : this.getRootMakefile().parsedMakefiles.values())
        {
            if(p.originalSourceFileNames!=null && p.originalSourceFileNames.size()>0)
            {
                for(String fileName: p.originalSourceFileNames)
                {
                    fileName=p.eval(fileName);
                    if(fileName.startsWith("/")==false)
                    {
                        fileName=p.myDirectoryPath+fileName;
                    }
                    System.out.println(fileName);
                }
            }
        }
    }
    public static void main(String[] argvs)
        throws Exception
    {
        ClearMakefile p = new ClearMakefile(null
                                              //,"D:\\copspf2\\Makefile"
                                              ," D:\\copspf2\\copneusrpvn\\nupneo\\Makefile"
                                              ,DEBUG);
        p.parseFile();
        p.listAllSourceFolders();
    }
}
